/*
 ============================================================================
 Name        : hev-receiver.c
 Author      : Heiher <admin@heiher.info>
 Version     : 0.0.2
 Copyright   : Copyright (c) 2010 everyone.
 Description : Linkage 802.1x for Unix-like operating system.
 ============================================================================
 */

#include <pcap.h>

#include "hev-receiver.h"
#include "hev-server.h"

#define HEV_RECEIVER_BUFFER_SIZE		1024
#define HEV_RECEIVER_TIMEOUT			500
#define HEV_RECEIVER_FILTER				"ether dst %02x:%02x:%02x:%02x:%02x:%02x and ether proto 0x888e"	/* 0x888e is IEEE 802.1x */

enum
{
	PROP_0,
	PROP_INTERFACE,
	N_PROPERTIES
};

enum
{
	RECEIVED,
	LAST_SIGNAL
};

static guint hev_receiver_signals[LAST_SIGNAL] = { 0 };
static GParamSpec * hev_receiver_properties[N_PROPERTIES] = { NULL };

static gboolean hev_receiver_channel_halder(GIOChannel * channel, GIOCondition cond, gpointer data);

#define HEV_RECEIVER_GET_PRIVATE(obj)	(G_TYPE_INSTANCE_GET_PRIVATE((obj), HEV_TYPE_RECEIVER, HevReceiverPrivate))

typedef struct _HevReceiverPrivate HevReceiverPrivate;

struct _HevReceiverPrivate
{
	pcap_t * pcap;
	GIOChannel * channel;
	guint io_source;
	gchar * interface;
};

static void hev_receiver_initable_iface_init(GInitableIface * iface);
static gboolean hev_receiver_initable_init(GInitable * initable,
					GCancellable * cancellable, GError ** error);
static void hev_receiver_set_property(GObject * obj, guint id,
				const GValue * value, GParamSpec * pspec);
static void hev_receiver_get_property(GObject * obj, guint id,
				GValue * value, GParamSpec * pspec);

G_DEFINE_TYPE_WITH_CODE(HevReceiver, hev_receiver, G_TYPE_OBJECT,
		G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, hev_receiver_initable_iface_init));

static void hev_receiver_dispose(GObject * obj)
{
	HevReceiver * self = HEV_RECEIVER(obj);
	HevReceiverPrivate * priv = HEV_RECEIVER_GET_PRIVATE(self);

	/* io_source START */
	if(0 < priv->io_source)
	{
		g_source_remove(priv->io_source);

		priv->io_source = 0;
	}
	/* io_source END */

	/* channel START */
	if(priv->channel)
	{
		g_io_channel_unref(priv->channel);

		priv->channel = NULL;
	}
	/* channel END */

	G_OBJECT_CLASS(hev_receiver_parent_class)->dispose(obj);
}

static void hev_receiver_finalize(GObject * obj)
{
	HevReceiver * self = HEV_RECEIVER(obj);
	HevReceiverPrivate * priv = HEV_RECEIVER_GET_PRIVATE(self);

	/* pcap START */
	if(priv->pcap)
	{
		pcap_close(priv->pcap);

		priv->pcap = NULL;
	}
	/* pcap END */

	G_OBJECT_CLASS(hev_receiver_parent_class)->finalize(obj);
}

static GObject * hev_receiver_constructor(GType type, guint n, GObjectConstructParam * param)
{
	return G_OBJECT_CLASS(hev_receiver_parent_class)->constructor(type, n, param);
}

static void hev_receiver_constructed(GObject * obj)
{
}

static void hev_receiver_class_init(HevReceiverClass * klass)
{
	GObjectClass * obj_class = G_OBJECT_CLASS(klass);

	obj_class->constructor = hev_receiver_constructor;
	obj_class->constructed = hev_receiver_constructed;
	obj_class->dispose = hev_receiver_dispose;
	obj_class->finalize = hev_receiver_finalize;
	obj_class->set_property = hev_receiver_set_property;
	obj_class->get_property = hev_receiver_get_property;

	hev_receiver_properties[PROP_INTERFACE] =
			g_param_spec_string("interface", "Interface", "Network Interface",
					NULL, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);

	g_object_class_install_properties(obj_class, N_PROPERTIES, hev_receiver_properties);

	hev_receiver_signals[RECEIVED] = g_signal_new("received",
			G_TYPE_FROM_CLASS(klass),
			G_SIGNAL_RUN_LAST,
			G_STRUCT_OFFSET(HevReceiverClass, received),
			NULL, NULL,
			g_cclosure_marshal_VOID__POINTER,
			G_TYPE_NONE, 1,
			G_TYPE_POINTER);

	g_type_class_add_private(klass, sizeof(HevReceiverPrivate));
}

static void hev_receiver_initable_iface_init(GInitableIface * iface)
{
	iface->init = hev_receiver_initable_init;
}

static void hev_receiver_init(HevReceiver * self)
{
	HevReceiverPrivate * priv = HEV_RECEIVER_GET_PRIVATE(self);

	priv->io_source = 0;
}

static gboolean hev_receiver_initable_init(GInitable * initable,
					GCancellable * cancellable, GError ** error)
{
	HevReceiver * self = HEV_RECEIVER(initable);
	HevReceiverPrivate * priv = HEV_RECEIVER_GET_PRIVATE(self);
	gchar errbuf[PCAP_ERRBUF_SIZE];

	if(NULL == priv->interface)
	{
		g_set_error(error, g_quark_from_string("hev-receiver"), 1, "Interface is NULL!");
		return FALSE;
	}

	priv->pcap = pcap_open_live(priv->interface, HEV_RECEIVER_BUFFER_SIZE,
			1, HEV_RECEIVER_TIMEOUT, errbuf);
	if(NULL == priv->pcap)
	{
		g_set_error(error, g_quark_from_string("hev-receiver"), 2, "%s", errbuf);
		return FALSE;
	}

	priv->channel = g_io_channel_unix_new(pcap_fileno(priv->pcap));
	if(NULL == priv->channel)
	{
		g_set_error(error, g_quark_from_string("hev-receiver"), 3, "Open fileno failed!");
		return FALSE;
	}

	priv->io_source = g_io_add_watch(priv->channel, G_IO_IN, hev_receiver_channel_halder, self);
	if(0 == priv->io_source)
	{
		g_set_error(error, g_quark_from_string("hev-receiver"), 4, "Add a watch failed!");
		return FALSE;
	}

	return TRUE;
}

static void hev_receiver_set_property(GObject * obj, guint id,
				const GValue * value, GParamSpec * pspec)
{
	HevReceiver * self = HEV_RECEIVER(obj);
	HevReceiverPrivate * priv = HEV_RECEIVER_GET_PRIVATE(self);

	switch(id)
	{
	case PROP_INTERFACE:
		{
			g_free(priv->interface);
			priv->interface = g_strdup(g_value_get_string(value));
			g_object_notify(obj, "interface");
			break;
		}
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, pspec);
		break;
	}
}

static void hev_receiver_get_property(GObject * obj, guint id,
				GValue * value, GParamSpec * pspec)
{
	HevReceiver * self = HEV_RECEIVER(obj);
	HevReceiverPrivate * priv = HEV_RECEIVER_GET_PRIVATE(self);

	switch(id)
	{
	case PROP_INTERFACE:
		{
			if(priv->interface)
			{
				g_value_set_string(value, priv->interface);
			}
			else
			{
				g_value_set_string(value, "");
			}
			break;
		}
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, pspec);
		break;
	}
}

GObject * hev_receiver_new(GCancellable * cancellable, GError ** error, gchar * interface)
{
	return g_initable_new(HEV_TYPE_RECEIVER, cancellable, error, "interface", interface, NULL);
}

gint hev_receiver_set_filter(HevReceiver * self, guchar * mac)
{
	HevReceiverPrivate * priv = NULL;
	GString * filter = g_string_new(NULL);
	struct bpf_program fp;
	gint i = 0;

	g_return_val_if_fail(HEV_IS_RECEIVER(self), -1);
	g_return_val_if_fail(mac, -1);

	priv = HEV_RECEIVER_GET_PRIVATE(self);

	g_string_printf(filter, HEV_RECEIVER_FILTER, mac[0], mac[1],
			mac[2], mac[3], mac[4], mac[5]);
	i = pcap_compile(priv->pcap, &fp, filter->str, 0, 0);
	g_string_free(filter, TRUE);
	if(-1 == i)
	{
		g_critical("[%s -> pcap_compile]: %s", __FUNCTION__, pcap_geterr(priv->pcap));
		return -1;
	}

	i = pcap_setfilter(priv->pcap, &fp);
	pcap_freecode(&fp);
	if(-1 == i)
	{
		g_critical("[%s -> pcap_setfilter]: %s", __FUNCTION__, pcap_geterr(priv->pcap));
		return -1;
	}

	return 0;
}

static gboolean hev_receiver_channel_halder(GIOChannel * channel, GIOCondition cond, gpointer data)
{
	HevReceiver * receiver = HEV_RECEIVER(data);
	HevReceiverPrivate * receiver_priv = NULL;

	g_return_val_if_fail(HEV_IS_RECEIVER(receiver), FALSE);

	receiver_priv = HEV_RECEIVER_GET_PRIVATE(receiver);

	if(cond & G_IO_IN)
	{
		const guchar * packet = NULL;
		struct pcap_pkthdr * header = NULL;

		if(1 == pcap_next_ex(receiver_priv->pcap, &header, &packet))
		{
			g_signal_emit(G_OBJECT(receiver), hev_receiver_signals[RECEIVED],
					0, packet);
		}
	}

	return TRUE;
}
